/*
 *    This program is free software; you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation; either version 2 of the License, or
 *    (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program; if not, write to the Free Software
 *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/*
 * @(#)SplashWindow.java  2.2  2005-04-03
 *
 * Copyright (c) 2003-2005 Werner Randelshofer
 * Staldenmattweg 2, Immensee, CH-6405, Switzerland.
 * All rights reserved.
 *
 * This software is in the public domain.
 *
 */

package weka.gui;

import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.MediaTracker;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.net.URL;

/**
 * A Splash window.
 * <p>
 * Usage: MyApplication is your application class. Create a Splasher class which
 * opens the splash window, invokes the main method of your Application class,
 * and disposes the splash window afterwards. Please note that we want to keep
 * the Splasher class and the SplashWindow class as small as possible. The less
 * code and the less classes must be loaded into the JVM to open the splash
 * screen, the faster it will appear.
 * 
 * <pre>
 * class Splasher {
 * 	public static void main(String[] args) {
 * 		SplashWindow.splash(Startup.class.getResource(&quot;splash.gif&quot;));
 * 		MyApplication.main(args);
 * 		SplashWindow.disposeSplash();
 * 	}
 * }
 * </pre>
 * 
 * @author Werner Randelshofer
 * @author Mark Hall
 * @version $Revision: 7059 $
 */
public class SplashWindow extends Window {

	/** for serialization */
	private static final long serialVersionUID = -2685134277041307795L;

	/**
	 * The current instance of the splash window. (Singleton design pattern).
	 */
	private static SplashWindow m_instance;

	/**
	 * The splash image which is displayed on the splash window.
	 */
	private Image image;

	/**
	 * This attribute indicates whether the method paint(Graphics) has been
	 * called at least once since the construction of this window.<br>
	 * This attribute is used to notify method splash(Image) that the window has
	 * been drawn at least once by the AWT event dispatcher thread.<br>
	 * This attribute acts like a latch. Once set to true, it will never be
	 * changed back to false again.
	 * 
	 * @see #paint
	 * @see #splash
	 */
	private boolean paintCalled = false;

	/**
	 * Creates a new instance.
	 * 
	 * @param parent
	 *            the parent of the window.
	 * @param image
	 *            the splash image.
	 */
	private SplashWindow(Frame parent, Image image) {
		super(parent);
		this.image = image;

		// Load the image
		MediaTracker mt = new MediaTracker(this);
		mt.addImage(image, 0);
		try {
			mt.waitForID(0);
		} catch (InterruptedException ie) {
		}

		// Center the window on the screen
		int imgWidth = image.getWidth(this);
		int imgHeight = image.getHeight(this);
		setSize(imgWidth, imgHeight);
		Dimension screenDim = Toolkit.getDefaultToolkit().getScreenSize();
		setLocation((screenDim.width - imgWidth) / 2,
				(screenDim.height - imgHeight) / 2);

		// Users shall be able to close the splash window by
		// clicking on its display area. This mouse listener
		// listens for mouse clicks and disposes the splash window.
		MouseAdapter disposeOnClick = new MouseAdapter() {
			public void mouseClicked(MouseEvent evt) {
				// Note: To avoid that method splash hangs, we
				// must set paintCalled to true and call notifyAll.
				// This is necessary because the mouse click may
				// occur before the contents of the window
				// has been painted.
				synchronized (SplashWindow.this) {
					SplashWindow.this.paintCalled = true;
					SplashWindow.this.notifyAll();
				}
				dispose();
			}
		};
		addMouseListener(disposeOnClick);
	}

	/**
	 * Updates the display area of the window.
	 */
	public void update(Graphics g) {
		// Note: Since the paint method is going to draw an
		// image that covers the complete area of the component we
		// do not fill the component with its background color
		// here. This avoids flickering.

		paint(g);
	}

	/**
	 * Paints the image on the window.
	 */
	public void paint(Graphics g) {
		g.drawImage(image, 0, 0, this);
		// Notify method splash that the window
		// has been painted.
		// Note: To improve performance we do not enter
		// the synchronized block unless we have to.
		if (!paintCalled) {
			paintCalled = true;
			synchronized (this) {
				notifyAll();
			}
		}
	}

	/**
	 * Open's a splash window using the specified image.
	 * 
	 * @param image
	 *            The splash image.
	 */
	public static void splash(Image image) {
		if (m_instance == null && image != null) {
			Frame f = new Frame();

			// Create the splash image
			m_instance = new SplashWindow(f, image);

			// Show the window.
			m_instance.show();

			// Note: To make sure the user gets a chance to see the
			// splash window we wait until its paint method has been
			// called at least once by the AWT event dispatcher thread.
			// If more than one processor is available, we don't wait,
			// and maximize CPU throughput instead.
			if (!EventQueue.isDispatchThread()
					&& Runtime.getRuntime().availableProcessors() == 1) {
				synchronized (m_instance) {
					while (!m_instance.paintCalled) {
						try {
							m_instance.wait();
						} catch (InterruptedException e) {
						}
					}
				}
			}
		}
	}

	/**
	 * Open's a splash window using the specified image.
	 * 
	 * @param imageURL
	 *            The url of the splash image.
	 */
	public static void splash(URL imageURL) {
		if (imageURL != null) {
			splash(Toolkit.getDefaultToolkit().createImage(imageURL));
		}
	}

	/**
	 * Closes the splash window.
	 */
	public static void disposeSplash() {
		if (m_instance != null) {
			m_instance.getOwner().dispose();
			m_instance = null;
		}
	}

	/**
	 * Invokes the named method of the provided class name.
	 * 
	 * @param className
	 *            the name of the class
	 * @param methodName
	 *            the name of the method to invoke
	 * @param args
	 *            the command line arguments
	 */
	public static void invokeMethod(String className, String methodName,
			String[] args) {
		try {
			Class.forName(className)
					.getMethod(methodName, new Class[] { String[].class })
					.invoke(null, new Object[] { args });
		} catch (Exception e) {
			InternalError error = new InternalError(
					Messages.getInstance()
							.getString(
									"SplashWindow_InvokeMethod_Exception_InternalError_Text")
							+ methodName);
			error.initCause(e);
			throw error;
		}
	}

	/**
	 * Invokes the main method of the provided class name.
	 * 
	 * @param className
	 *            the name of the class
	 * @param args
	 *            the command line arguments
	 */
	public static void invokeMain(String className, String[] args) {
		try {
			Class.forName(className)
					.getMethod("main", new Class[] { String[].class })
					.invoke(null, new Object[] { args });
		} catch (Exception e) {
			InternalError error = new InternalError(
					Messages.getInstance()
							.getString(
									"SplashWindow_InvokeMain_Exception_InternalError_Text"));
			error.initCause(e);
			throw error;
		}
	}
}
